Upgrading dependencies strategy for Node.js project
This is an English version of this article
TL;DR
Three points to upgrade dependencies when building an application.
- Fix the versions of
dependencies
/devDependencies
. - Upgrade the dependent packages every month.
- Use
npm-check-updates
to upgrade dependent packages.
What is the dependencies for Node.js Project?
It is described in dependencies
and devDependencies
of package.json.
dependencies
specifies dependencies required for the package to run, and devDependencies
specifies dependencies required for developing the package. devDependencies
will not be installed if the package is installed as a dependency of any packages.
Specifying the version of the dependent package
In dependencies
and devDependencies
, the caret ^
U+005E is given at the beginning when a new dependency package is added without specifying any options, respectively, like followings.
// snip "devDependencies": { "typescript": "^3.8.3", }, // snip
For versions, Semantic Versioning has been adopted.
The leading caret indicates "Install the current latest version with the same number on the left". The specific implications can be easily understood by looking at the following table
Notation | Meanings |
---|---|
^1.3.4 |
latest version less than 2.0.0 |
^0.3.2 |
latest version less than 0.4.0 |
^0.0.4 |
latest version less than 0.0.5 |
^2.1.4-beta.3 |
latest version less than 3.0.0 |
^0.0.3-beta.3 |
latest version less than 0.0.4 |
You can also add a tilde ~
U+007E, which says "Install the current, latest version with the same minor version". If no minor version is specified, e.g. ~1
, it means the latest version with the same major version.
There are other ways to specify the version, but if you know these two types, there is no problem.
Fixing versions
As mentioned above, if you don't care about it, you'll end up with a wide range of package versions to be installed. This may cause different versions to be installed between team members, and your product may behave differently for different members.
As a mechanism to prevent this, npm provides package-lock.json and yarn provides yarn.lock.
These are generated to fix the structure of node_modules
when running npm install
or yarn
, respectively, and are shared by team members by management on Git. This prevents being installed different versions of dependent packages between members.
Fixing version explicitly
Although package-lock.json and yarn.lock fix the version of the dependent package, I personally believe that the version written in package.json should be fixed.
- It makes us become aware of the choices
- It isolates the confirmation when the version lock file is regenerated.
It makes us become aware of the choices
At the very least, you should be aware of what features and vulnerabilities there are for the packages that your project directly relies on. The project will be beyond your control if you don't know what features and bugs are being added and what to do with them.
Of course, any version of the dependency package can be used for verification and other projects that are discarded on the spot.
It isolates the confirmation when the version lock file is regenerated.
As described below, deleting version lock files allows you to upgrade dependent packages to the latest versions in a batch. In this case, the unintended disruptive changes caused by the upgrade of a directly dependent package will be unknown.
Methods to fix version explicitly
Remove ^
or ~
if it already exists in the version specification of dependencies
/ devDependencies
.
If you want to fix the version on a new installation, you may want to use the following options
Package manager | Option |
---|---|
npm | --save-exact |
yarn | --exact |
Use these as follows.
npm install --save-exact react // or yarn add --exact react
When to upgrade the version
When a dependent package is upgraded, it should be followed and used in its version, as its contents are generally more sophisticated. Here we will discuss when you should upgrade.
When a vulnerability is found in a dependent package
There's a feature on GitHub called Security alerts, which you can usually use. However, you have to agree to let GitHub inspect your code, so don't use it if you're worried about leaking information.
As a way to manually look for vulnerabilities, npm provides npm audit
/ yarn provides yarn audit
. If you hit these commands at hand, you'll see dependent packages that have vulnerabilities, so upgrade accordingly.
Every 1 month
Regularly upgrade dependent packages to add features and increase efficiency of processing content. Considering the speed at which npm packages are developed these days, it's a good idea to upgrade about every month. Too often, it's too much of a burden on the developer and won't last long, and over a longer span of time, many disruptive changes have to be considered at the same time, making upgrades a major undertaking.
You may want to discuss this with your team members and decide on a week to make the upgrade.
Upgrading versions
Ways to upgrade verions provided by framework
Some frameworks may provide its own upgrade methods.
Case: create-react-app
You can find [how to upgrade] (https://github.com/facebook/create-react-app/blob/master/CHANGELOG.md#migrating-from-340-to-341) for each version.
Case: React Native
A [dedicated tool] (https://react-native-community.github.io/upgrade-helper/) is provided.
You can specify the current version and which version you want to upgrade to to show your work.
Case: Vue.js
You can also find the [how to upgrade page] (https://cli.vuejs.org/migrating-from-v3/).
Removing and regenerating version lock files
You should do this first during regular upgrades.
rm -f package-lock.json npm i // or rm -f yarn.lock yarn
Dependent packages are now up to date. Depending on the specification of package.json, even if your upgrade is supposed to be backward compatible, you should make sure that it passes the various tests and check the operation manually.
Backward compatibility is very difficult to maintain, because the dependent packages may contain unintentionally destructive changes.
npm-check-updates
npm-check-updates is a tool that compares the current version used by the project with the latest version published in npm and prints out the differences. Use as following
npx npm-check-updates
The results are as follows.
$ npx npm-check-updates npx: installed 235 in 11.497s Checking /Users/takagi.kensuke/work/dev/classmethod/oss/liff-app-template/package.json [====================] 8/8 100% html-webpack-plugin 3.2.0 → 4.2.0 typescript 3.7.5 → 3.8.3 webpack 4.41.6 → 4.42.1 Run ncu -u to upgrade package.json
You can change all packages to the latest version by specifying the -u
option.
However, if there are many changes, it is recommended to upgrade each related package group and check its operation. Let's upgrade with granularity, for example, test packages, development packages, and runtime dependencies.
Don't forget to run npm i
or yarn
after the version change.
peerDependencies
Basically, dependencies should be the latest version, but they should satisfy peerDependencies
too. This is used to specify the version of the package to work with, and the version of the main package that the plugin or other tool runs on.
When upgrading to the latest version, such as npm-check-updates
, make sure to upgrade both plugins and the main package at the same time. Also, if a violation of peerDependencies
is found by npm i
/ yarn
after the latest upgrade, drop the version.
Refraining from using dependent upgrade tools.
There are some things out there that will detect changes when a dependent package is upgraded and generate a pull request, but you should refrain from using them. It is inevitable that you will have to upgrade dependent packages in any phase, and the pull requests created by those tools will get in the way.
At the very least, you should be aware of the upgrade.
Summary
Think of this upgrade strategy as just one way of doing things. This is a fairly safe operation, such as fixing the direct dependency package version.
Also keep in mind that the circumstances are different when creating a library, not an application. This is because the intended user is completely different from the application.